#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import threading
import queue
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
from PIL import Image

# -------------------- 软件信息 --------------------
SOFTWARE_NAME = "图片批量压缩工具"
VERSION = "1.0.0"
AUTHOR = "GWL"         
HOMEPAGE = "https://gitee.com/guowanglin/batch-image-compression-tool" 

# -------------------- 辅助函数 --------------------

def format_size(size_bytes):
    """将字节数转换为友好格式"""
    for unit in ['B', 'KB', 'MB', 'GB']:
        if size_bytes < 1024:
            return f"{size_bytes:.2f} {unit}"
        size_bytes /= 1024
    return f"{size_bytes:.2f} TB"

def process_transparency(img, output_format):
    """处理透明背景转换，当目标格式为 JPEG 时添加白色背景"""
    if img.mode in ('RGBA', 'LA') and output_format.lower() in ('jpg', 'jpeg'):
        background = Image.new('RGB', img.size, (255, 255, 255))
        background.paste(img, mask=img.split()[-1])
        return background
    return img

def compress_image(input_path, output_path, quality=85, conflict='rename', out_format="auto"):
    """
    压缩单张图片：
      - quality: 压缩质量（1-100）
      - conflict: 冲突处理方式（'skip'、'overwrite'、'rename'）
      - out_format: 输出格式（"auto" 保持原格式，或指定 "jpg"/"png"/"webp"）
    """
    try:
        original_output = output_path
        counter = 1
        # 若选择重命名处理冲突，则不断尝试修改文件名
        while os.path.exists(output_path) and conflict == 'rename':
            base, ext = os.path.splitext(original_output)
            output_path = f"{base}_{counter}{ext}"
            counter += 1

        if os.path.exists(output_path):
            if conflict == 'skip':
                return False, "跳过已存在文件"
            elif conflict == 'abort':
                raise FileExistsError("文件已存在")

        with Image.open(input_path) as img:
            original_format = img.format
            if out_format != "auto":
                output_ext = out_format
            else:
                output_ext = original_format if original_format else 'JPEG'
            output_ext = output_ext.lower()
            img = process_transparency(img, output_ext)

            save_kwargs = {'quality': quality}
            if output_ext == 'webp':
                save_kwargs['method'] = 6

            img.save(output_path, **save_kwargs)

        orig_size = os.path.getsize(input_path)
        new_size = os.path.getsize(output_path)
        ratio = (1 - new_size/orig_size) * 100

        return True, f"{format_size(orig_size)} → {format_size(new_size)} (-{ratio:.1f}%)"
        
    except Exception as e:
        return False, str(e)

# -------------------- GUI 主程序 --------------------

class ImageCompressorGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("图片批量压缩工具")
        self.root.geometry("700x550")
        self.log_queue = queue.Queue()
        self.create_menu()

        # 定义变量
        self.input_type = tk.StringVar(value="folder")  # "file" 或 "folder"
        self.input_path = tk.StringVar()
        self.output_path = tk.StringVar()
        self.quality = tk.IntVar(value=85)
        self.recursive = tk.BooleanVar(value=False)
        self.conflict = tk.StringVar(value="重命名")  # 默认显示中文“重命名”
        self.out_format = tk.StringVar(value="auto")
        self.progress_count = tk.IntVar(value=0)
        self.total_files = 0

        # 建立界面
        self.create_widgets()
        # 开启定时轮询队列
        self.root.after(100, self.poll_queue)

    def create_menu(self):
        """创建菜单栏，并增加‘关于’菜单项显示软件信息"""
        menubar = tk.Menu(self.root)
        help_menu = tk.Menu(menubar, tearoff=0)
        help_menu.add_command(label="软件信息", command=self.show_about)
        help_menu.add_command(label="使用帮助", command=self.show_help)
        menubar.add_cascade(label="帮助", menu=help_menu)
        self.root.config(menu=menubar)

    def show_about(self):
        """显示软件信息"""
        info = (f"{SOFTWARE_NAME}\n"
                f"版本: {VERSION}\n"
                f"作者: {AUTHOR}\n"
                f"地址: {HOMEPAGE}")
        messagebox.showinfo("关于", info)

    def show_help(self):
        """显示使用帮助信息"""
        help_text = ("使用说明:\n"
                     "1. 选择输入类型（单个文件或整个文件夹），并选择输入路径。\n"
                     "2. 选择输出路径。\n"
                     "3. 设置压缩质量、是否递归处理、文件冲突处理方式及输出格式。\n"
                     "4. 点击‘开始压缩’按钮，等待进度完成。\n"
                     "5. 查看右侧日志信息获取每个文件的处理结果。")
        messagebox.showinfo("使用帮助", help_text)

    def create_widgets(self):
        frame = ttk.Frame(self.root, padding="10")
        frame.pack(fill=tk.BOTH, expand=True)

        # 标题和说明
        title = ttk.Label(frame, text="图片批量压缩工具", font=("微软雅黑", 16, "bold"))
        title.grid(row=0, column=0, columnspan=4, pady=5)
        desc = ttk.Label(frame, text="支持单个文件或整个文件夹的图片批量压缩，操作简单。", foreground="gray")
        desc.grid(row=1, column=0, columnspan=4, pady=5)

        # 选择输入类型
        ttk.Label(frame, text="输入类型:").grid(row=2, column=0, sticky=tk.W, pady=5)
        rb_file = ttk.Radiobutton(frame, text="单个文件", variable=self.input_type, value="file")
        rb_folder = ttk.Radiobutton(frame, text="文件夹", variable=self.input_type, value="folder")
        rb_file.grid(row=2, column=1, sticky=tk.W)
        rb_folder.grid(row=2, column=2, sticky=tk.W)

        # 输入路径
        ttk.Label(frame, text="输入路径:").grid(row=3, column=0, sticky=tk.W, pady=5)
        self.input_entry = ttk.Entry(frame, textvariable=self.input_path, width=50)
        self.input_entry.grid(row=3, column=1, columnspan=2, sticky=tk.W)
        btn_input = ttk.Button(frame, text="选择", command=self.select_input)
        btn_input.grid(row=3, column=3, padx=5)

        # 输出路径
        ttk.Label(frame, text="输出路径:").grid(row=4, column=0, sticky=tk.W, pady=5)
        self.output_entry = ttk.Entry(frame, textvariable=self.output_path, width=50)
        self.output_entry.grid(row=4, column=1, columnspan=2, sticky=tk.W)
        btn_output = ttk.Button(frame, text="选择", command=self.select_output)
        btn_output.grid(row=4, column=3, padx=5)

        # 压缩质量
        ttk.Label(frame, text="压缩质量 (1-100):").grid(row=5, column=0, sticky=tk.W, pady=5)
        self.quality_scale = ttk.Scale(frame, from_=1, to=100, orient=tk.HORIZONTAL,
                                       variable=self.quality, command=self.update_quality)
        self.quality_scale.grid(row=5, column=1, columnspan=3, sticky=tk.EW, pady=5)
        quality_label = ttk.Label(frame, textvariable=self.quality)
        quality_label.grid(row=5, column=4, sticky=tk.W)

        # 递归处理（仅对文件夹有效）
        self.recursive_cb = ttk.Checkbutton(frame, text="递归处理子文件夹", variable=self.recursive)
        self.recursive_cb.grid(row=6, column=0, columnspan=2, sticky=tk.W, pady=5)

        # 文件冲突处理方式（中文选项）
        ttk.Label(frame, text="冲突处理方式:").grid(row=7, column=0, sticky=tk.W, pady=5)
        conflict_options = ["重命名", "跳过", "覆盖"]
        self.conflict_combo = ttk.Combobox(frame, values=conflict_options, state="readonly", textvariable=self.conflict, width=10)
        self.conflict_combo.grid(row=7, column=1, sticky=tk.W, padx=5)

        # 输出格式（调整padx使标签和控件靠近）
        ttk.Label(frame, text="输出格式:").grid(row=7, column=2, sticky=tk.W, pady=5, padx=0)
        format_options = ["auto", "jpg", "png", "webp"]
        self.format_combo = ttk.Combobox(frame, values=format_options, state="readonly", textvariable=self.out_format, width=10)
        self.format_combo.grid(row=7, column=3, sticky=tk.W, padx=0)

        # 开始按钮
        self.start_button = ttk.Button(frame, text="开始压缩", command=self.start_compression)
        self.start_button.grid(row=8, column=0, columnspan=4, pady=10)

        # 进度条
        self.progress_bar = ttk.Progressbar(frame, maximum=100, variable=self.progress_count)
        self.progress_bar.grid(row=9, column=0, columnspan=4, sticky=tk.EW, pady=5)

        # 日志输出框
        ttk.Label(frame, text="日志信息:").grid(row=10, column=0, sticky=tk.W, pady=5)
        self.log_text = scrolledtext.ScrolledText(frame, height=10, state=tk.DISABLED)
        self.log_text.grid(row=11, column=0, columnspan=4, sticky=tk.NSEW, pady=5)

        # 配置网格权重，使日志框随着窗口变化而扩展
        frame.rowconfigure(11, weight=1)
        frame.columnconfigure(2, weight=1)

    def update_quality(self, val):
        """回调函数：在拖动压缩质量滑动块时将浮点数转换为整数"""
        self.quality.set(int(float(val)))

    def select_input(self):
        """根据输入类型选择文件或文件夹"""
        if self.input_type.get() == "file":
            file_path = filedialog.askopenfilename(
                title="选择输入文件", 
                filetypes=[("图片文件", "*.png *.jpg *.jpeg *.bmp *.gif *.webp"), ("所有文件", "*.*")]
            )
            if file_path:
                self.input_path.set(file_path)
        else:
            folder_path = filedialog.askdirectory(title="选择输入文件夹")
            if folder_path:
                self.input_path.set(folder_path)

    def select_output(self):
        """选择输出文件夹"""
        folder_path = filedialog.askdirectory(title="选择输出文件夹")
        if folder_path:
            self.output_path.set(folder_path)

    def start_compression(self):
        """开始压缩操作，将参数传入后台线程处理"""
        inp = self.input_path.get().strip()
        outp = self.output_path.get().strip()
        if not inp or not os.path.exists(inp):
            messagebox.showerror("错误", "请输入有效的输入路径！")
            return
        if not outp:
            messagebox.showerror("错误", "请选择输出路径！")
            return

        # 禁用开始按钮，避免重复点击
        self.start_button.config(state=tk.DISABLED)
        self.progress_count.set(0)
        self.log_text.config(state=tk.NORMAL)
        self.log_text.delete("1.0", tk.END)
        self.log_text.config(state=tk.DISABLED)

        # 启动后台线程处理压缩
        thread = threading.Thread(target=self.worker, daemon=True)
        thread.start()

    def worker(self):
        """
        后台处理函数：
          - 根据输入类型生成文件列表
          - 遍历每个文件进行压缩，并将处理进度和日志信息发送到队列
        """
        input_path = self.input_path.get().strip()
        output_dir = self.output_path.get().strip()
        quality = self.quality.get()
        recursive = self.recursive.get()
        # 将中文冲突处理选项映射为内部使用的英文参数
        conflict_map = {"重命名": "rename", "跳过": "skip", "覆盖": "overwrite"}
        conflict = conflict_map[self.conflict.get()]
        out_format = self.out_format.get()

        allowed_ext = ('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.webp')
        file_list = []

        if self.input_type.get() == "file":
            file_list.append(input_path)
        else:
            for root, _, files in os.walk(input_path):
                for f in files:
                    if f.lower().endswith(allowed_ext):
                        file_list.append(os.path.join(root, f))
                if not recursive:
                    break

        total = len(file_list)
        self.total_files = total
        self.log_queue.put(("log", f"共找到 {total} 个图片文件待处理...\n"))
        if total == 0:
            self.log_queue.put(("log", "没有找到符合条件的图片文件！\n"))
            self.log_queue.put(("done", None))
            return

        success = 0
        skip = 0
        error = 0
        count = 0

        for file_path in file_list:
            # 根据输入路径构造输出路径
            if self.input_type.get() == "file":
                out_file = os.path.join(output_dir, os.path.basename(file_path))
            else:
                rel_path = os.path.relpath(file_path, input_path)
                out_file = os.path.join(output_dir, rel_path)
            # 如果用户指定了输出格式，则修改文件扩展名
            if out_format != "auto":
                base, _ = os.path.splitext(out_file)
                out_file = f"{base}.{out_format}"

            # 创建输出目录（若不存在）
            os.makedirs(os.path.dirname(out_file), exist_ok=True)

            ok, msg = compress_image(file_path, out_file, quality=quality, conflict=conflict, out_format=out_format)
            if ok:
                success += 1
                log_msg = f"[成功] {os.path.basename(file_path)}：{msg}\n"
            else:
                if "跳过" in msg:
                    skip += 1
                else:
                    error += 1
                log_msg = f"[失败] {os.path.basename(file_path)}：{msg}\n"
            count += 1
            self.log_queue.put(("log", log_msg))
            self.log_queue.put(("progress", count))
        summary = f"\n处理完成：成功 {success} 个，跳过 {skip} 个，失败 {error} 个。\n"
        self.log_queue.put(("log", summary))
        self.log_queue.put(("done", None))

    def poll_queue(self):
        """定时轮询线程消息队列，更新日志和进度条"""
        try:
            while True:
                action, value = self.log_queue.get_nowait()
                if action == "log":
                    self.log_text.config(state=tk.NORMAL)
                    self.log_text.insert(tk.END, value)
                    self.log_text.see(tk.END)
                    self.log_text.config(state=tk.DISABLED)
                elif action == "progress":
                    progress = int((value / self.total_files) * 100)
                    self.progress_count.set(progress)
                elif action == "done":
                    self.start_button.config(state=tk.NORMAL)
        except queue.Empty:
            pass
        self.root.after(100, self.poll_queue)

# -------------------- 主程序入口 --------------------

if __name__ == "__main__":
    root = tk.Tk()
    app = ImageCompressorGUI(root)
    root.mainloop()
